Skip to content

pat-reload - Reload DOM structures at event triggers - #1300

Draft
thet wants to merge 22 commits into
masterfrom
thet/pat-reload
Draft

thet wants to merge 22 commits into
masterfrom
thet/pat-reload

Conversation

@thet

@thet thet commented Nov 9, 2026

Copy link
Copy Markdown
Member

Draft to create a Pattern to reload dom structures on event triggers.

E.g. reload toolbar on navigatesuccessevent.

This is a draft including documentation of the architecture/development process (ADR) and some fixes to pat-inject.

Needs to be taken up again.

Current status: Architecturing the Mockup pat-base-url Pattern.

thet added 22 commits November 6, 2026 21:15
…fill.

The basic Navigation API polyfill now also fires a `navigatesuccess` event
after `pushState` and `replaceState`, in addition to `navigate`.

Note: Unlike the native implementation, the polyfill fires `navigate` after
the URL has changed. Listening to `navigatesuccess` gives consistent timing
for native and polyfilled implementations.

Ref: ADR 0003
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
pat-inject is a non-class based pattern - the registry calls `init` on the
same plain object for every element. Storing the UUID on `this` meant every
element initialization overwrote it. Create the UUID locally where it is
needed instead: for the delayed autoload trigger and the autoload-navigate
listener id.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
…lling it.

The autoload-navigate trigger called `onTrigger` immediately during
initialization and passed its return value (undefined) as the event listener.
Pass a callback instead. The navigate event is deliberately not passed to
`onTrigger`, which would call `preventDefault` on it and cancel the
navigation.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
…L changes only.

Listen to `navigatesuccess` instead of `navigate`. `navigate` is fired
before the navigation is committed - the URL is not yet updated and the
navigation might still be cancelled or unload the page.

Only re-trigger when the URL without the hash has changed. This ignores
anchor jumps and the same-URL `replaceState` calls which pat-inject uses to
mark history entries ("pageload", "anchor").

Do not initialize if the Navigation API is not available.

Ref: ADR 0003
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
…y: record.

Recording history pushes a new history entry, which fires a navigation event
which would re-trigger the injection again - an infinite loop. Log an error
and do not initialize the trigger in that case.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
…elements.

When the element was removed from the DOM - e.g. replaced by an injection -
remove the navigation event listener instead of triggering an injection on a
detached element.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
…ning injections.

Rapid successive navigations are debounced using the `delay` option
(default: 0).

pat-inject drops triggers while an injection is in progress. For
autoload-navigate this would leave the content in the state of an outdated
URL. Instead, remember a pending trigger and run it once after the running
injection has finished.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Reloading content on navigation or other events will be implemented in a
dedicated pattern `pat-reload`, which uses pat-inject for the injection.

Doing it in pat-inject would have required changing pat-inject's general
contract - resolving the `url` relative to the current location and
allowing other elements than `<a>` and `<form>` - for one use case, while
the "when and which URL to reload" logic is not about injection.

The polyfill's `navigatesuccess` event is kept.

Ref: ADR 0001
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
pat-reload reloads the content of an element when an event is fired - by
default `navigatesuccess`, after same-document navigations which changed
the URL. This is useful for toolbars, breadcrumbs or sidebars which depend on
the current location.

pat-reload defines when and from which URL to reload. The injection itself
is delegated to pat-inject and can be configured via `data-pat-inject` on
the same element. By default, the content is injected into the element
itself.

Options:
- `url`: Required. Relative URLs are appended to the current URL (without
  query string and hash), absolute URLs are used as-is.
- `trigger-event`: Default `navigatesuccess`. Navigation API events are
  listened to on `window.navigation`, all others on `document`.
- `delay`: Debounce time for successive events.

Hash changes and same-URL replaceState calls do not trigger a reload. Events
during a running reload trigger one more reload afterwards. Listeners are
removed when the element was removed from the DOM.

The base URL for relative URLs is the current location for now. A
configurable base URL - e.g. for Plone default pages - will follow.

Ref: ADR 0001
Ref: ADR 0002
Ref: ADR 0003
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Ref: ADR 0001
Ref: ADR 0002
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Ref: ADR 0001
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
…e and pat-inject-finished event.

- `execute` now returns `true` if the injection was started and `false`
  otherwise - for invalid configurations, denied confirmations or when an
  injection is already in progress. Previously it returned `undefined`.

- A native `pat-inject-finished` event is dispatched on the pat-inject
  element after the injection has finished - successfully or not.

Until now the injection state was only available via jQuery: the
`pat-inject-triggered` jQuery data and the `pat-ajax-success` and
`pat-ajax-error` events, which are triggered via jQuery and cannot be
listened to with native event listeners.

Ref: ADR 0004
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Only missing `#id` targets can be created. For other missing targets,
`createTarget` returned `null` but `ensureTarget` still reported success.
`execute` then failed with a TypeError when adding the loading class to the
non-existing target. Now the configuration is reported as invalid and the
injection is not started.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
…tate.

Track running reloads with the return value of pat-inject's `execute` and
the native `pat-inject-finished` event, registered via
`events.add_event_listener`, instead of reading the `pat-inject-triggered`
jQuery data and listening to the jQuery-triggered `pat-ajax-success` and
`pat-ajax-error` events.

`pat-inject-finished` events bubbling up from injections within the element
are ignored.

jQuery is only used for calling pat-inject's API, which expects jQuery
objects.

Ref: ADR 0004
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
…en the injection has finished.

This allows to use async/await with pat-inject:

    try {
        const injected = await inject.execute(cfgs, $el);
    } catch (error) {
        log.warn("Could not load.", error);
    }

The Promise:
- resolves to `true` if the content was injected,
- resolves to `false` if the injection was not started - invalid
  configuration, denied confirmation or an injection already in progress -
  or if there was nothing to inject,
- rejects with an Error if the request failed with a network error or an
  HTTP error status. The error's `cause` is the jqXHR object. pat-inject's
  own error handling is done before.

The `pat-inject-finished` event is dispatched right before the Promise
settles. The Promise also waits for injections with a `process-delay`.

This replaces the boolean return value from the previous commit.

Note: Code which calls `execute` without handling the rejection now gets
unhandled Promise rejections for failed requests. The internal callers in
pat-inject, pat-collapsible and pat-expandable-tree catch them, as the error
is already handled by pat-inject.

Ref: ADR 0004
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Await the Promise returned by `inject.execute` instead of listening to the
`pat-inject-finished` event. This removes the event listener and the
filtering of events bubbling up from injections within the element.

Failed reloads are logged as warnings and do not block further reloads.

Ref: ADR 0004
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
pat-ajax only handles the result of the latest request, e.g. when a search
form is submitted multiple times in a row. The requests were counted per URL
globally though. When two different elements requested the same URL at the
same time, the first element never received a `pat-ajax-success` event.

For pat-inject this meant, that the first element stayed in the "injection
in progress" state forever and could not inject again, and the Promise
returned by `inject.execute` never settled.

Now the requests are counted per element. The latest request of an element
still wins - also when the same element requests different URLs, so that an
outdated response cannot overwrite a newer one.

Ref: ADR 0005
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
…s Promise API and pat-ajax request tracking.

- ADR 0001: Reload content on events with pat-reload instead of a pat-inject trigger.
- ADR 0002: pat-reload URL resolution relative to the current location.
- ADR 0003: React to navigations on navigatesuccess, not navigate.
- ADR 0004: pat-inject's execute returns a Promise.
- ADR 0005: pat-ajax handles the latest request per element.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
@thet
thet marked this pull request as draft November 9, 2026 17:24
@thet thet changed the title pat-reload pat-reload - Reload DOM structures at event triggers Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant